import unittest
import argparse
import json
import os
from unittest.mock import patch, mock_open
from engine_server.config.base import ServerConfig, IConfig, BaseConfig, supported_engine


class TestServerConfig(unittest.TestCase):
    """Tests for ServerConfig dataclass"""

    def test_default_values(self):
        """Test default values of ServerConfig"""
        config = ServerConfig()
        self.assertEqual(config.server_host, "127.0.0.1")
        self.assertEqual(config.server_port, 9001)
        self.assertEqual(config.engine_type, "vllm")
        self.assertIsNone(config.config_path)
        self.assertEqual(config.dp_rank, 0)

    def test_custom_values(self):
        """Test ServerConfig with custom values"""
        custom_config = ServerConfig(
            server_host="127.0.0.1",
            server_port=8080,
            engine_type="vllm",
            config_path="/path/to/config.json",
            dp_rank=1,
        )
        self.assertEqual(custom_config.server_host, "127.0.0.1")
        self.assertEqual(custom_config.server_port, 8080)
        self.assertEqual(custom_config.engine_type, "vllm")
        self.assertEqual(custom_config.config_path, "/path/to/config.json")
        self.assertEqual(custom_config.dp_rank, 1)
    
    def test_validate_valid_engine_type(self):
        """Test validate() with valid engine_type"""
        config = ServerConfig(
            engine_type="vllm",
            config_path="/fake/path.json"
        )
        with patch('os.path.exists', return_value=True), \
             patch('engine_server.config.base.FileValidator') as mock_validator:
            mock_validator_instance = mock_validator.return_value
            mock_validator_instance.check_not_soft_link.return_value = mock_validator_instance
            mock_validator_instance.check_file_size.return_value = mock_validator_instance
            mock_validator_instance.check.return_value = mock_validator_instance
            mock_validator_instance.is_valid.return_value = True

            try:
                config.validate()
            except ValueError:
                self.fail("validate() raised ValueError unexpectedly for valid engine_type")
    
    def test_validate_invalid_engine_type(self):
        """Test validate() with invalid engine_type"""
        config = ServerConfig(engine_type="invalid_engine")
        with self.assertRaises(ValueError) as context:
            config.validate()
        self.assertIn("invalid_engine is not supported", str(context.exception))
    
    def test_validate_valid_dp_rank(self):
        """Test validate() with valid dp_rank"""
        config = ServerConfig(
            engine_type="vllm",
            dp_rank=5,
            config_path="/fake/path.json"
        )
        with patch('os.path.exists', return_value=True), \
             patch('engine_server.config.base.FileValidator') as mock_validator:
            mock_validator_instance = mock_validator.return_value
            mock_validator_instance.check_not_soft_link.return_value = mock_validator_instance
            mock_validator_instance.check_file_size.return_value = mock_validator_instance
            mock_validator_instance.check.return_value = mock_validator_instance
            mock_validator_instance.is_valid.return_value = True
            
            try:
                config.validate()
            except ValueError:
                self.fail("validate() raised ValueError unexpectedly for valid dp_rank")
    
    def test_validate_invalid_dp_rank(self):
        """Test validate() with invalid dp_rank"""
        # dp_rank < 0
        config = ServerConfig(engine_type="vllm", dp_rank=-1)
        with self.assertRaises(ValueError) as context:
            config.validate()
        self.assertIn("-1 is not supported", str(context.exception))
        
        # dp_rank > 65535
        config = ServerConfig(engine_type="vllm", dp_rank=70000)
        with self.assertRaises(ValueError) as context:
            config.validate()
        self.assertIn("70000 is not supported", str(context.exception))
    
    def test_validate_config_file_not_exist(self):
        """Test validate() when config file does not exist"""
        config = ServerConfig(
            engine_type="vllm",
            config_path="/fake/nonexistent/path.json"
        )
        with patch('os.path.exists', return_value=False):
            with self.assertRaises(ValueError) as context:
                config.validate()
            self.assertIn("config file /fake/nonexistent/path.json does not exist", str(context.exception))
    
    def test_validate_invalid_config_file(self):
        """Test validate() when config file exists but is invalid"""
        config = ServerConfig(
            engine_type="vllm",
            config_path="/fake/path.json"
        )
        with patch('os.path.exists', return_value=True), \
             patch('engine_server.config.base.FileValidator') as mock_validator:
            mock_validator_instance = mock_validator.return_value
            mock_validator_instance.check_not_soft_link.return_value = mock_validator_instance
            mock_validator_instance.check_file_size.return_value = mock_validator_instance
            mock_validator_instance.check.return_value = mock_validator_instance
            mock_validator_instance.is_valid.return_value = False
            
            with self.assertRaises(ValueError) as context:
                config.validate()
            self.assertIn("/fake/path.json is not a valid file path", str(context.exception))
    
    def test_supported_engine_constant(self):
        """Test supported_engine constant contains expected values"""
        self.assertIn("vllm", supported_engine)
        self.assertEqual(len(supported_engine), 1)


class TestIConfig(unittest.TestCase):
    """Tests for IConfig abstract base class"""

    def test_abstract_methods(self):
        """Test that IConfig cannot be instantiated directly"""
        with self.assertRaises(TypeError):
            IConfig()  # 抽象类不能实例化

    def test_concrete_subclass(self):
        """Test a minimal concrete subclass of IConfig"""
        class ConcreteConfig(IConfig):
            def validate(self):
                pass
            def convert(self):
                pass
            def get_args(self):
                return argparse.Namespace()
            def get_server_config(self):
                return ServerConfig()

        config = ConcreteConfig()
        self.assertIsInstance(config, IConfig)
        self.assertIsInstance(config.get_server_config(), ServerConfig)
        self.assertIsInstance(config.get_args(), argparse.Namespace)


class TestBaseConfig(unittest.TestCase):
    """Tests for BaseConfig class"""

    def setUp(self):
        self.server_config = ServerConfig(
            server_host="localhost",
            server_port=9001,
            engine_type="vllm"
        )
        self.base_config = BaseConfig(server_config=self.server_config)

    def test_initialization(self):
        """Test BaseConfig initialization"""
        self.assertEqual(self.base_config.server_config, self.server_config)
        self.assertEqual(self.base_config.kv_config, {})

    @patch('engine_server.config.base.ServerConfig.validate')
    def test_validate_calls_server_config_validate(self, mock_validate):
        """Test validate() calls server_config.validate()"""
        self.base_config.validate()
        mock_validate.assert_called_once()

    @patch('builtins.open', new_callable=mock_open, read_data='{"key": "value", "nested": {"inner": 42}}')
    def test_load_config_file_success(self, mock_file):
        """Test successful loading of config file"""
        server_config = ServerConfig(config_path="/fake/path.json")
        config = BaseConfig(server_config=server_config)
        config._load_config_file()

        mock_file.assert_called_once_with("/fake/path.json", 'r', encoding='utf-8')
        self.assertEqual(config.kv_config, {"key": "value", "nested": {"inner": 42}})

    @patch('builtins.open', side_effect=IOError("File not found"))
    def test_load_config_file_io_error(self, mock_file):
        """Test error handling when config file load fails with IOError"""
        server_config = ServerConfig(config_path="/invalid/path.json")
        config = BaseConfig(server_config=server_config)
        
        with self.assertRaises(ValueError) as context:
            config._load_config_file()
        
        self.assertIn("failed to load config file: File not found", str(context.exception))
    
    @patch('builtins.open', new_callable=mock_open, read_data='invalid json')
    def test_load_config_file_json_error(self, mock_file):
        """Test error handling when config file contains invalid JSON"""
        server_config = ServerConfig(config_path="/invalid/json.json")
        config = BaseConfig(server_config=server_config)
        
        with self.assertRaises(ValueError):
            config._load_config_file()

    @patch.object(BaseConfig, '_load_config_file')
    def test_convert_calls_load_config_file(self, mock_load):
        """Test convert() calls _load_config_file()"""
        self.base_config.convert()
        mock_load.assert_called_once()

    def test_get_args(self):
        """Test get_args() returns correct Namespace"""
        self.base_config.kv_config = {"arg1": "val1", "arg2": 2, "arg3": True}
        args = self.base_config.get_args()
        self.assertIsInstance(args, argparse.Namespace)
        self.assertEqual(args.arg1, "val1")
        self.assertEqual(args.arg2, 2)
        self.assertEqual(args.arg3, True)

    def test_get_server_config(self):
        """Test get_server_config() returns ServerConfig from kv_config"""
        self.base_config.kv_config = {
            "server_host": "192.168.1.1",
            "server_port": 9002,
            "engine_type": "vllm",
            "dp_rank": 2
        }
        server_config = self.base_config.get_server_config()
        self.assertIsInstance(server_config, ServerConfig)
        self.assertEqual(server_config.server_host, "192.168.1.1")
        self.assertEqual(server_config.server_port, 9002)
        self.assertEqual(server_config.engine_type, "vllm")
        self.assertEqual(server_config.dp_rank, 2)

    def test_get_server_config_with_partial_config(self):
        """Test get_server_config() with partial configuration"""
        self.base_config.kv_config = {
            "server_port": 8888,
            "dp_rank": 1
        }
        # 其他字段应该使用默认值
        server_config = self.base_config.get_server_config()
        self.assertEqual(server_config.server_port, 8888)
        self.assertEqual(server_config.dp_rank, 1)
        self.assertEqual(server_config.server_host, "127.0.0.1")  # 默认值
        self.assertEqual(server_config.engine_type, "vllm")  # 默认值


if __name__ == "__main__":
    unittest.main()